#include <sys/stat.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

#include "rsc.h"
#include "storage.h"
#include "record.h"
#include "ff_wrap.h"

static int record_runflag = 0;
static pthread_t record_tid;
static struct rsc_record_param rec_param;
/** maybe is temporary */
static struct rsc_record_param user_rec_param;
static int param_has_changed = 1;

int rsc_get_rec_params(struct rsc_record_param *param)
{
	/** TODO: wrap UPS-layer API */
	if(param)
		memcpy(param,&user_rec_param,sizeof(user_rec_param));
	return RSC_OK;
}

int rsc_set_rec_params(struct rsc_record_param *param)
{
	if(param)
	{
		memcpy(&user_rec_param,param,sizeof(user_rec_param));
		/** TODO: diff @param@ with current @rec_param@ */
		/** TODO: wrap UPS-layer API */
		param_has_changed = 1;
	}
	return RSC_OK;
}

static int get_record_duration(st_rsc_rec_ctrl *pctrl)
{
	int duration = pctrl->file_time; // second

	if(pctrl->mode == REC_MODE_TIMER)
	{
		duration = pctrl->mode_param.timer_end - pctrl->mode_param.timer_start;
	}
	else if(pctrl->mode == REC_MODE_EVENT)
	{
		duration = pctrl->mode_param.event_time;
	}
	else if(pctrl->mode == REC_MODE_CYCLE)
		duration = pctrl->mode_param.cycle_time;
	else 
		duration = pctrl->file_time;

	return duration;
}
static int get_record_file_size(struct rsc_record_param *param)
{
	int total_filesize = 0;
	int brate = param->media.video_bitrate/8; // KB/s
	int duration = get_record_duration(&param->control);
	int chn = param->source.chn;

	total_filesize = brate * duration * chn;
	return total_filesize;
}

static int replace_string_substr(char *s, char *s1, char *s2)
{
	if(s==NULL || s1==NULL || s2==NULL)
		return RSC_EFAIL;

	char *psubstr = NULL;
	int s1_len = strlen(s1);
	int s2_len = strlen(s2);
	if(s1_len < s2_len)
	{
		zlog_error(zc_rsc,"s1 len[%d] < s2 len[%d] is unsafe!\n",s1_len,s2_len);
		return RSC_EFAIL;
	}
	while((psubstr = strstr(s,s1)))
	{
		int i = 0;
		char *p = psubstr;
		char *q = psubstr+s1_len;
		for(; *p&& i<s2_len;p++,i++)
			*p = *s2++;
		for(;*q;q++) 
			*p++ = *q;
		*p = '\0';
	}
	return RSC_OK;
}
static int parse_record_file_name(const char *format, char *name,int len)
{
	if(format == NULL || name == NULL || len <= 0)
		return RSC_EPARA;
	/** suppose format like : "YYYYMMDD/hhmmss_nn.avi",the keywords is :
	 * 'YYYY','MM','DD','hh','mm','ss' - are date-time,'nn'-is channel id */
	char string[64]={0};

	memset(name,0,len);
	strcpy(string,format);
	replace_string_substr(string,"YYYY","%Y");
    replace_string_substr(string,"MM","%m");
    replace_string_substr(string,"DD","%d");
    replace_string_substr(string,"hh","%H");
    replace_string_substr(string,"mm","%M");
    replace_string_substr(string,"ss","%S");

    time_t now = time(NULL);
    if(!strftime(name,len,string,localtime(&now))) /* name may include "nn" */
		return RSC_EFAIL;

	return RSC_OK;
}

static int rsc_prepare_record(struct rsc_record_param *param,struct record_context_info *context)
{
	int ret = 0;
	/** step1 : calculate record file total size and get record root path */
	int total_filesize = get_record_file_size(param);
	char root_path[64]={0};
	zlog_info(zc_rsc,"total_filesize = %d Bytes\n",total_filesize);
	ret = rsc_get_avail_dev(total_filesize, root_path);
	if(ret != RSC_OK)
	{
		zlog_error(zc_rsc,"can't get avail dev for record,size = %dB\n",total_filesize);
		return RSC_EFAIL;
	}

	/** step2 : parse record relative path and file name from param->control */
	char file_path[64]={0};
	char file_name[64]={0};
#if 0 /** if add mode directory would make storage loop-delete failed
		* the rsc_storage design may need to be modified 
		*/
	snprintf(file_path,sizeof(file_path),"%s/%s",root_path,param->control.mode_desc);
	mkdir(file_path,0777); // ensure has mode-directory
#endif
	snprintf(file_path,sizeof(file_path),"%s",root_path);
	parse_record_file_name(param->control.file_format,file_name,sizeof(file_name));
	zlog_info(zc_rsc,"parse file_name:%s\n",file_name);

	/** step3: create file directory if need */
	char file[128]={0};
	char *pslash = NULL;
	snprintf(file,sizeof(file),"%s/%s",file_path,file_name);
	pslash = strrchr(file,'/');
	if(pslash!=NULL)
	{
		char cmd[128]={0};
		*pslash = 0; // set to null
		snprintf(cmd,sizeof(cmd),"mkdir -p %s",file);
		system(cmd); 
	}
	strcpy(context->filepath,file_path);
	strcpy(context->filename,file_name);

	/** step4 : init record context with parameter in param->media & control */
	st_ff_context *ff_ctx = NULL;
	int i = 0;

    replace_string_substr(file_name,"nn","%d");
	for(i = 0;i < param->source.chn;i++)
	{
		/* the channel [i] record file name */
		snprintf(context->filename,sizeof(context->filename),file_name,i+1);
		zlog_info(zc_rsc,"chn file_name:%s\n",context->filename);
		snprintf(file,sizeof(file),"%s/%s",context->filepath,context->filename);
		ff_ctx = (st_ff_context*)context->context[i];
		if(ff_init_muxer(file,ff_ctx,param)<0)
		{
			zlog_error(zc_rsc,"ff_init_muxer [%d] error!\n",i);
			ret = RSC_EFAIL;
			break;
		}
	}

	return ret;
}

#define REC_MAX_FRAME_SIZE (1024*1024)
/** a static buffer or may be malloced in the init part. */
static unsigned char rec_frame_buff[REC_MAX_FRAME_SIZE]={0};
static int rsc_write_frame(struct record_data_source *data,struct record_context_info *context)
{
	int ret = 0;
	int i = 0;
	struct record_frame_info frame;

	memset(&frame,0,sizeof(frame));
	frame.addr = &rec_frame_buff[0];
	memset(frame.addr,0,REC_MAX_FRAME_SIZE);
	frame.size = REC_MAX_FRAME_SIZE;

	st_ff_context *ff_ctx = NULL;
	for(i=0;i<data->chn;i++)
	{
		ff_ctx = (st_ff_context*)context->context[i];

		if(data->preprocess[REC_DTYPE_VIDEO])
		{
			/** step1 : get a frame from data source assigned */
			data->preprocess[REC_DTYPE_VIDEO](&frame,data->video_ds[i]);
			/** step2 : write the frame in record file using context info */
			if(frame.len > 0)
				ret = ff_write_video_frame(ff_ctx,frame.addr,frame.len);
		}
		if(data->preprocess[REC_DTYPE_AUDIO])
		{
			data->preprocess[REC_DTYPE_VIDEO](&frame,data->video_ds[i]);
			if(frame.len > 0)
				ret = ff_write_audio_frame(ff_ctx,frame.addr,frame.len);
		}
	}

	return ret;
}
static int rsc_finish_record(int chns, struct record_context_info *context)
{
	st_ff_context *ff_ctx = NULL;
	int i = 0;

	for(i=0;i<chns;i++)
	{
		ff_ctx = (st_ff_context*)context->context[i];
		ff_exit_muxer(ff_ctx);
	}

	return RSC_OK;
}

static void *rsc_record_process(void *arg)
{
	st_rsc_rec_ctrl *pctrl = &rec_param.control;
	//st_rsc_rec_media *pmedia = &rec_param.media;
	st_rsc_rec_data *pdata = &rec_param.source;
	int rec_need_fini = 0;
	int rec_status = REC_STAT_INIT;
	struct record_context_info rec_context;
	int rec_chn_cnt = pdata->chn;

	memset(&rec_context,0,sizeof(rec_context));
	void *pctx = calloc(rec_chn_cnt,sizeof(st_ff_context));
	if(pctx ==NULL)
	{
		zlog_error(zc_rsc,"malloc st_ff_context error!\n");
		return (void*)-1;
	}
	int i = 0;
	for(;i<rec_chn_cnt;i++)
	{
		rec_context.context[i] = pctx+i*sizeof(st_ff_context);
	}

	time_t s_time = time(NULL);
	while(record_runflag)
	{
		switch(rec_status)
		{
			case REC_STAT_INIT:
			{
				if(param_has_changed)
				{
					/* TODO:memcpy params from ram */
					memcpy(&rec_param,&user_rec_param,sizeof(user_rec_param));
					param_has_changed = 0;
					rec_status = REC_STAT_CHECK;
				}
				else
				{
					rec_status = REC_STAT_WRITE;
				}
				break;
			}
			case REC_STAT_CHECK:
			{
				if(rec_need_fini)
				{
					rec_status = REC_STAT_FINI;
				}
				else if(pctrl->on)
				{
					/* TODO: other params check */
					rec_status = REC_STAT_PREPARE;
				}
				else
				{
					rec_status = REC_STAT_INIT;
				}
				break;
			}
			case REC_STAT_PREPARE:
			{
				if(rsc_prepare_record(&rec_param,&rec_context)<0)
				{
					zlog_error(zc_rsc,"rsc_prepare_record failed!\n");
					rec_status = REC_STAT_EXIT;
				}
				else
				{
					s_time = time(NULL);
					rec_status = REC_STAT_WRITE;
				}
				break;
			}
			case REC_STAT_WRITE:
			{
				rsc_write_frame(pdata,&rec_context);
				rec_need_fini = 1;
				if(time(NULL)-s_time>=get_record_duration(pctrl))
					rec_status = REC_STAT_FINI;
				else
					rec_status = REC_STAT_INIT;
				break;
			}
			case REC_STAT_FINI:
			{
				rsc_finish_record(rec_chn_cnt,&rec_context);
				rec_need_fini = 0;
				rec_status = REC_STAT_CHECK;
				break;
			}
			case REC_STAT_EXIT:
			{
				record_runflag = 0;
				break;
			}
		}
		usleep(10000);
	}

	if(rec_need_fini)
		rsc_finish_record(rec_chn_cnt,&rec_context);
	if(pctx)
		free(pctx);
	memset(&rec_context,0,sizeof(rec_context));

	return NULL;
}

int rsc_init_record(struct rsc_record_param *param)
{
	if(!record_runflag)
	{
		record_runflag = 1;
	}
	else
	{
		zlog_warn(zc_rsc,"rsc_init_record has been finished!\n");
		return RSC_EFAIL;
	}

	if(zc_rsc==NULL)
	{
		fprintf(stderr,"must init rsc storage module first!\n");
		return RSC_EFAIL;
	}

	if(param == NULL)
	{
		zlog_error(zc_rsc,"invalid params!\n");
		return RSC_EFAIL;
	}
	memcpy(&rec_param,param,sizeof(struct rsc_record_param));
	memcpy(&user_rec_param,param,sizeof(struct rsc_record_param));

	if(pthread_create(&record_tid,NULL,rsc_record_process,NULL)<0)
	{
		zlog_error(zc_rsc,"create record process thread error!\n");
		return RSC_EFAIL;
	}
	return RSC_OK;
}

void rsc_exit_record(void)
{
	if(record_runflag)
	{
		record_runflag = 0;
		pthread_join(record_tid,NULL);
	}
}

int rsc_start_record(void)
{
	return RSC_OK;
}

int rsc_stop_record(void)
{
	return RSC_OK;
}

